#include <string>

/**
 * Ring buffer with read and write pointers
 */
template<unsigned int capacity> class BoundedBuffer {

	private:
		unsigned char buffer[capacity]; /**< byte buffer that stores the actual data */
		unsigned int bytesAvail; /**< number of bytes available in buffer */
		unsigned int writePosition; /**< current write position */
		bool containsNL; /**< true if buffer contains a newline character */
		unsigned int positionNL; /**< position of the newline character if containsNL==true */

	public:
		BoundedBuffer() {
			bytesAvail = 0;
			writePosition = 0;
			containsNL = false;
			positionNL = 0;
		}

		/**
		 * how many bytes fit in the ring buffer?
		 */
		unsigned int getCapacity() const {
			return capacity;
		}

		/**
		 * current write position
		 */
		unsigned int getWritePtr() const {
			return writePosition;
		}

		/**
		 * current read position
		 */
		unsigned int getReadPtr(unsigned int offset = 0) const {
			return (writePosition - bytesAvail + capacity + offset) % capacity;
		}

		/**
		 * write count bytes of data
		 * @return true on success
		 */
		bool put(const char* data, unsigned int count) {
			if (count + bytesAvail > capacity) return false;
			if (!containsNL) {
				for (unsigned int i = 0; i < count; i++) {
					if (data[i] == '\n') {
						containsNL = true;
						positionNL = bytesAvail + i;
						break;
					}
				}
			}
			if (count + writePosition <= capacity) {
				/* Put a single chunk */
				memcpy(buffer + writePosition, data, count);
			} else {
				/* Put two chunks */
				memcpy(buffer + writePosition, data, capacity - writePosition);
				memcpy(buffer, data + capacity - writePosition, count - (capacity - writePosition));
			}
			writePosition = (writePosition + count) % capacity;
			bytesAvail += count;
			return true;
		}

		/**
		 * write data
		 */
		bool put(const std::string& data) {
			return put(data.c_str(), data.size());
		}

		/**
		 * inspect next count bytes of data
		 * @return true on success
		 */
		bool getChunk(unsigned int count, char* data) const {
			if (count > bytesAvail) return false;
			unsigned int rPtr = getReadPtr();
			if (rPtr + count <= capacity) {
				memcpy(data, buffer + rPtr, count);
			} else {
				memcpy(data, buffer + rPtr, capacity - rPtr);
				memcpy(data + capacity - rPtr, buffer, count - (capacity - rPtr));
			}
			return true;
		}

		/**
		 * inspect buffer at arbitrary position
		 */
		unsigned char get(unsigned int atPos) const {
			if (atPos > bytesAvail) return -1;
			return buffer[(capacity + writePosition - bytesAvail + atPos) % capacity];
		}

		/**
		 * discard all data and reset buffer
		 */
		void clear() {
			bytesAvail = 0;
			containsNL = false;
		}

		/**
		 * mark count bytes as read
		 */
		void remove(unsigned int count) {
			if (count >= bytesAvail) {
				clear();
			} else {
				bytesAvail -= count;
				containsNL = false;
				for (unsigned int i = 0; i < getFill(); i++) {
					if (get(i) == '\n') {
						containsNL = true;
						positionNL = i;
						break;
					}
				}
			}
		}

		/**
		 * get number of bytes that can be read
		 */
		unsigned int getFill() const {
			return bytesAvail;
		}

		/**
		 * get number of bytes that can be written
		 */
		unsigned int getFree() const {
			return capacity - bytesAvail;
		}

		/**
		 * provides direct access to byte buffer
		 */
		const unsigned char* getRawBuffer() const {
			return buffer;
		}

		/**
		 * is there a newline character in the buffer?
		 */
		bool completeLine() const {
			return containsNL;
		}

		/**
		 * read and return data up until next newline
		 */
		std::string getLine() {
			if (!containsNL) return "";

			if (positionNL == 0) {
				remove(1);
				return "";
			}

			unsigned int oldPositionNL = positionNL;
			char tempBuf[oldPositionNL + 1];
			if (!getChunk(oldPositionNL, tempBuf)) return "";
			remove(oldPositionNL + 1);
			tempBuf[oldPositionNL] = 0;
			return std::string(tempBuf);
		}

		/**
		 * inspect internals of buffer
		 */
		void dump() const {
			printf("Size: %3d, Fill: %3d, Free: %3d, Pos: %3d, HaveLine: %s @ %d\n", capacity, getFill(), getFree(), writePosition, completeLine() ? "yes" : "no", positionNL);
			for (unsigned int i = 0; i < capacity; i++) {
				unsigned char c = buffer[i];
				printf(" %s %s %3d (%3d): '%c' (0x%02x)\n",
				       i == getWritePtr() ? "W->" : " ",
				       i == getReadPtr() ? "R->" : " ",
				       i,
				       (i - getReadPtr() + capacity) % capacity,
				       c < 32 || c > 127 ? '.' : c,
				       c);
			}
		}
};

